#!/usr/bin/python
# -*-coding: utf-8 -*-

# This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

u"""
This module provides functions for simple and frequent tasks like type 
conversions or type check.

TODO: check functions
"""

__all__ = [
           u'CREATED_PATHS',
           u'Stopwatch',
           u'age_to_date',
           u'arrange_alias',
           u'arrange_path',
           u'backup_file',
           u'check_ascii',
           u'check_bool',
           u'check_implements',
           u'check_iterable',
           u'check_keys',
           u'check_pyvar_naming_convention',
           u'check_type',
           u'check_value',
           u'check_var_naming_convention',
           u'checktopath',
           u'cmp_path_date',
           u'error',
           u'find_file',
           u'get_data',
           u'get_datatest',
           u'get_text_file_format',
           u'good_level',
           u'good_level_2',
           u'is_binary',
           u'isimplementation',
           u'list2',
           u'list_to_unicode_list',
           u'make_alias',
           u'mkdtempu',
           u'readfile',
           u'safe_replaceable',
           u'str2unicode',
           u'to_qt_tt',
           u'to_xml',
           u'unicode2',
           u'unicode2_to_ASCIIstr',
           u'unicode2_to_str',
           u'unicode_list_to_float_list',
           u'uprint',
           u'walk_files',
           u'writefile',
           ]

import re
import sys
import time
import codecs
import pickle
import tempfile
import datetime
import pkg_resources
import inspect

from _pylot.minimal.path import Path, DirPath, FilePath
from _pylot.minimal.system import ENCODING, error, uprint
from _pylot.data import regexpr

from traits import interface_checker

def arrange_path(path, path_class=Path):
  u"""
  Return a Path, FilePath or DirPath dependings on path nature.
  Path is used for special path like device "files" or path not existing on disk.
  If path is empty, returns None.
  
  If path do not exists on disk or is not file nor directory 
  (like /dev/xyz on linux),it return a path_class.
  """
  if not path :
    return None
  path = Path(unicode(path))
  if path.isfile():
    return FilePath(path)
  elif path.isdir():
    return DirPath(path)
  else :
    return path_class(path)


def age_to_date(minutes, hours=0, days=0):
  u"""
  Transform an age in days, hours minutes in datetime.
  For example, if you know you modify a file in last 5 minutes, age_to_date
  give you the date 5 minute before.
  This method uses :obj:`datetime.timedelta`.
  """
  today = datetime.datetime.today()
  delta = datetime.timedelta(days=days, hours=hours, minutes=minutes)
  return today - delta

def cmp_path_date(path1, path2):
  return cmp(path1.mtime_datetime(), path2.mtime_datetime())

def to_xml (text):
  u"""
  Convert a string to a valid unicode string.
  An empty non basestring method is converted to u''
  
  >>> to_xml('<salut> ')
  u'&lt;salut&gt;'
  
  This method uses :obj:`xml.dom.minidom.Document.createTextNode`.
  """
  from xml.dom.minidom import Document
  doc = Document()
  if isinstance(text, str):
    text = text.decode(ENCODING)
  elif isinstance(text, unicode):
    pass
  elif text :
    text = unicode(text)
  else :
    return u''
  return doc.createTextNode(text).toxml()

def to_qt_tt(text):
  u"""
  Stands for "to qt tooltip". Convert text to a valid xml text.
  If text is a boolean, converts it to "checked/uncheckd"
  """
  if isinstance(text, bool) :
    if text is True :
      return u'checked'
    else :
      return u'unchecked'
  else :
    return to_xml(text)

def mkdtempu (suffix=u'', prefix=u'tmp', dir=None):
  u"""
  Like :meth:`tempfile.mkdtemp` but returns :class:`~pyLot.core.Path` instead
  of :obj:`str` and takes unicode parameters instead of str.
  """
  check_type(u'suffix', suffix, unicode)
  check_type(u'prefix', prefix, unicode)

  suffix = suffix.encode(ENCODING, u'replace')
  prefix = prefix.encode(ENCODING, u'replace')
  path = tempfile.mkdtemp(suffix, prefix, dir)
  upath = Path(path.decode(ENCODING)).abspath()
  assert(unicode(upath).encode(ENCODING) == path)
  return upath

def make_alias (obj):
  u"""
  Generates an alias for given obj.
  
  :param obj: object with name and alias attributes. Typically Datum or Container
  """
  if hasattr(obj, 'name') and hasattr(obj, 'alias'):
    if obj.alias :
      return obj.alias
    else :
      return obj.name.capitalize()
  else :
    msg = 'The instance %r has nor name or alias attrbutes' % obj
    raise ValueError, msg

def list_to_unicode_list (src, to_unicode=unicode):
  u"""
  Take a list as input and return same list with all values converted to unicode.
  This method proceeds recursively.
  
  >>> lst = [1,True,[3,None]]
  >>> list_to_unicode_list(lst)
  [u'1', u'True', [u'3', u'None']]
  
  You can specify to_unicode function to change unicode conversion behaviour.
  For example :
  
  >>> list_to_unicode_list(lst, to_unicode=unicode2)
  [u'1', u'True', [u'3', u'']]
  """
  check_type(u'src', src, list)
  new_list = []
  for i, val in enumerate(src) :
    if isinstance(val, list) :
      new_list.append(list_to_unicode_list(val))
    elif isinstance(val, str) :
      new_list.append(to_unicode(val.decode(ENCODING)))
    else :
      new_list.append(to_unicode(val))
  return new_list

def unicode_list_to_float_list (src):
  u"""
  Take a list as input and return same list with all values converted to float.
  This method proceeds recursively.
  
  Example :

  >>> unicode_list_to_float_list([u'1', 2, [u'3', u'4']])
  [1.0, 2.0, [3.0, 4.0]]
  
  If a value cannot be converted, it standard value error is raised.

  >>> unicode_list_to_float_list([u'1', u'a'])
  Traceback (most recent call last) :
  ...
  ValueError: could not convert string to float: a
  """
  check_type(u'src', src, list)
  for i, val in enumerate(src) :
    if isinstance(val, list) :
      src[i] = unicode_list_to_float_list(val)
    else :
      src[i] = float(val)
  return src

def str2unicode(s, encoding=ENCODING):
  u"""
  Converts s into unicode string using encoding.
  If s is None, returns None.
  """
  if s is None :
    return None
  else :
    return s.decode(ENCODING)

def unicode2(s, encoding=ENCODING):
  u"""
  returns u'' if s is None else unicode(s)
  """
  if isinstance(s, unicode) :
    return s
  elif s is None :
    return u''
  else :
    return str(s).decode(encoding, u'replace')

def unicode2_to_str(s, encoding=ENCODING):
  u"""
  Like unicode2 but returns str instead of unicode
  """
  if isinstance(s, unicode) :
    return s.encode(encoding, u'replace')
  elif s is None :
    return ''
  else :
    return str(s)

def unicode2_to_ASCIIstr (s):
  u"""
  Like unicode2 but returns ASCII str instead of unicode
  """
  if isinstance(s, unicode) :
    return s.encode(u'ASCII', u'replace')
  elif s is None :
    return ''
  else :
    return str(s)

def list2(lst):
  u"""
  returns [] if s is None else list(s)
  """
  if lst is None:
    return []
  else:
    return list(lst)

def check_iterable (varname, value):
  u"""
  >>> check_iterable(u'var', [])
  >>> check_iterable(u'var', 1)
  Traceback (most recent call last):
  ...
  TypeError: E001 - var: iterable object expected, got int
  >>> check_iterable(u'var', u"abc")
  Traceback (most recent call last):
  ...
  TypeError: E001 - var: iterable object expected, got unicode
  """
  if type(varname) != unicode :
    raise TypeError, error(tr_(u'E001 - varname: unicode expected, got %(TYPE)s') % dict(
      TYPE=type(varname).__name__))
  if not isinstance(value, (set, list, tuple)) :
    raise TypeError, error(tr_(u'E001 - %(VARNAME)s: iterable object expected, '\
      u'got %(TYPE)s') % dict(VARNAME=varname, TYPE=type(value).__name__))

def isimplementation(obj, interface_list):
  u"""
  Returns True if obj implements all interfaces of interface_list.
  Use this function like "isinstance" but to check implementation instead of 
  type.
  """
  if inspect.isclass(obj):
    klass = obj
  else :
    klass = obj.__class__

  try :
    bases = klass.__implements__.getBases()
  except AttributeError:
    return False
  else :
    if not isinstance(interface_list, (tuple, list)):
      interface_list = [interface_list]
      for interface in interface_list :
        if interface not in bases :
          return False
  return True

def check_implements(varname, value, interface_list, quick=False, none=False):
  u"""
  Checks obj implements interfaces.
  If no, raises an error.
  Use this function like "check_type" function.

  quick=False (default) :
    - checks if class declare implementing interfaces
    - checks all methods and arguments
  quick=True :
    - checks only if class declare implementing interfaces 
    quick can be 50-100 time faster than default mode
    (or even more for complex interfaces) but you MUST TRUST IN developers who
    write class.
    
  .. warning ::
  
    Even with full check (quick=False), this function cannot check return values
    are valid or behaviour is correct.

  :param none: if True, None value are considered valid
  """
  if none is True and value is None :
    return
  if inspect.isclass(value):
    klass = value
  else :
    klass = value.__class__

  if quick :
    c_obj = klass.__name__
    try :
      bases = klass.__implements__.getBases()
    except AttributeError:
      msg = 'The instance %r of class %r does not implement interfaces' % (varname, c_obj)
      raise interface_checker.InterfaceError(msg)
    else :
      if not isinstance(interface_list, (tuple, list)):
        interface_list = [interface_list]
        for interface in interface_list :
          if interface not in bases :
            c_i = interface.__name__
            msg = 'The instance %r of class %r does not implement the %r interface' % (varname, c_obj, c_i)
            raise interface_checker.InterfaceError(msg)
  else :
    interface_checker.check_implements(klass, interface_list, 2)

def checktopath(path):
  u"""
  Function checks path type and returns a :class:`~pyLot.core.Path`.
  Path may not actually exists on disk.
  """
  check_type(u'path', path, unicode)
  return Path(path)

def check_bool(varname, value, none=False):
  u"""
  Check if value is a boolean in sense of pyLot :
  u'true', u'on', u'TRUE', 1, ... are valid values for True
  Sure, True/False are valid booleans.
  """
  if isinstance(value, unicode):
    value = value.strip().lower()
    if value in(u'true', u'on', u'1') :
      return True
    elif value in(u'false', u'off', u'0') :
      return False
    else :
      return check_type(varname, value, bool, none=none)

  else :
    return check_type(varname, value, bool, none=none)

def check_type (varname, value, type_list, subclass=True, none=False,
                exception=None):
  u"""
  Checks parameter type.
  
  :param varname: variable name, the name displayed in error messages. Useful for debug
  :type varname: `unicode`
  :param value: value to test (the variable)
  :type value: all types
  :param type_list: list of allowed types
  :type type_list: `list` or type
  :subclass: if True[default], accept subclasses of allowed types.
  :type subclass: `bool`
  :param none: if True, also accept None value (NoneType type)
  :type none: `bool`
  """
  if type(varname) != unicode :
    raise TypeError, error(tr_(u'E005 - varname: unicode expected, got %(TYPE)s') % dict(
      TYPE=type(varname).__name__))

  if type(subclass) != bool :
    raise TypeError, error(tr_(u'E001 - subclass: bool expected, got %(TYPE)s') % dict(
      TYPE=type(subclass).__name__))

  if type(none) != bool :
    raise TypeError, error(tr_(u'E001 - none: bool expected, got %s(TYPE)') % dict(
      TYPE=type(none).__name__))

  if isinstance(type_list, (list, tuple)) :
    type_list = list(type_list)
  else :
    type_list = [type_list]

  if none is True :
    type_list.append(type(None))

  matches = False
  if subclass is True :
    for ttype in type_list :
      if isinstance(value, ttype) :
        matches = True
        break
      else :
        continue
  else :
    for ttype in type_list :
      if type(value) is ttype :
        matches = True
        break
      else :
        continue

  if not matches :
    type_name_list = [unicode(ttype.__name__) for ttype in type_list]
    type_name_list.sort()
    dic = dict(varname=varname, type=type(value).__name__, value=value)
    if exception :
      raise exception(**dic)
      return
    if type_name_list == [u'Path', u'unicode'] or type_name_list == [u'NoneType', u'Path', u'unicode'] :
      ERROR = u'E006'
    elif type_name_list == [u'unicode'] :
      ERROR = u'E005'
    else :
      ERROR = u'E001'

    type_names = u', '.join(type_name_list)
    dic['TYPES'] = type_names
    dic['ERROR'] = ERROR
    dic['VARNAME'] = varname
    dic['TYPE'] = type(value).__name__
    dic['VALUE'] = value
#    raise TypeError, error(tr_(u'%(ERROR)s - %(VARNAME)s: %(TYPES)s expected, got %(TYPE)s (%(VALUE)s)') % dic)
    raise TypeError, error(tr_(u'%(ERROR)s - %(VARNAME)s: %(TYPES)s expected, got %(TYPE)s') % dic)

  return value

def check_value(varname, value, value_list=[]):
  u"""
  checks parameter values.
  Even if value_list as only one value, value_list must be a list.
  

  Example :

  >>> check_value(u'mesh_dim', -1, [1, 2, 3])
  Traceback (most recent call last):
  ...
  ValueError: E002 - mesh_dim: 1, 2, 3 expected, got -1

  :param varname: variable name, useful for debug
  :param value: value to test
  :param value_list: list of allowed values
  """
  assert(hasattr(value_list, '__iter__'))

  if value not in value_list :
    values = u', '.join([repr(val).decode(ENCODING, u'replace') for val in value_list])
    raise ValueError, error(tr_(u'E002 - %(VARNAME)s: %(VALUES)s expected, got %(VALUE)s') % dict(
      VARNAME=varname, VALUES=values, VALUE=repr(value).decode(ENCODING, u'replace')))

def check_keys(varname, dic, keys):
  u"""
  Check if dic contains given keys.
  
  >>> dic = dict(data1=1)
  >>> check_keys(u'dic', dic, [u'data1', u'data2'])
  Traceback (most recent call last) :
  ...
  dict "dic" must contains keys : data1, data2. key "data2" is missing  
  """
  check_type(u'varname', varname, unicode)
  check_type(u'dic', dic, dict)
  check_type(u'keys', keys, list)
  for key in keys :
    if not dic.has_key(key) :
      raise ValueError, error(tr_(u'dict "%(NAME)s" must contains keys : %(KEYS)s. key "%(KEY)s" is missing') % (
        dict(NAME=varname, KEYS=u', '.join(keys), KEY=key)))

def good_level_2(current_level, option_level):
  level_dict = {u'hidden':5, u'expert':4, u'user':1, u'devel':2}
  return good_level(option_level, level_dict, current_level)

def good_level (option_level, level_dict, current_level):
  u"""
  Returns True if manager level is greater or equal than given level

  Order is defined by level dict.
  Ex1 (user interface level) : 
    level_dict = {'user':1, 'devel':2, 'devel':3, 'expert':4}
  Ex2 (display level) : 
    level_dict = {'silent':1, 'standard':2, 'verbose':3}
  
  The greater, the more (displayed, done, ...)

  For example, if we want to try an devel option :

  if good_level('devel', level_dict, current_level) :
    print "only in user devel and higher"
    ...

  The answer depends on current interface level :
    if current_level is "user", return False
                        "devel-user", return True
                        "devel", return True
                        "expert", return True

  :param option_level: level required, must be a string supported by level_dict 
  (ex: user)
  :type option_level: ASCII string
  """
  if type(option_level) is unicode :
    if option_level not in level_dict.keys() :
      uprint(tr_(u'level %s not available, consider option not available') % option_level,
             file=sys.stderr)
      return False
    else :
      option_level = level_dict[option_level]
  elif type(option_level) is bool :
    uprint(tr_(u'level %s not available, consider option not available') % option_level,
           file=sys.stderr)
  if level_dict[current_level] >= option_level :
    return True
  else :
    return False

def is_binary(filepath):
  """Return true if the given filepath is binary.
  :raise EnvironmentError: if the file does not exist or cannot be accessed.
  :attention: found @ http://bytes.com/topic/python/answers/21222-determine-file-type-binary-text on 6/08/2010
  :author: Trent Mick <TrentM@ActiveState.com>
  :author: Jorge Orpinel <jorge@orpinel.com>"""
  fin = open(filepath, 'rb')
  try:
    CHUNKSIZE = 1024
    while True:
      chunk = fin.read(CHUNKSIZE)
      if '\0' in chunk:  # found null byte
        return True
      if len(chunk) < CHUNKSIZE:
        break  # done
  # A-wooo! Mira, python no necesita el "except:". Achis... Que listo es.
  finally:
    fin.close()

  return False

def get_text_file_format(filepath, nmax=20):
  u"""
  Returns end line type : 'win', 'mac', 'nix'.
  If path seems binary, returns 'bin'.
  
  If path cannot be read, it raises standard open('file', 'r') error
  """
  lines = [None for i in range(nmax)]
  if is_binary(filepath) :
    fmt = 'bin'
  else :
    f = open(filepath, 'r')
    lines[0] = l = f.readline()
    if l.endswith('\r\n') :
      fmt = 'win'
    elif l.endswith('\r') :
      fmt = 'mac'
    elif l.endswith('\n') :
      fmt = 'nix'
    else :
      fmt = 'other'
  return fmt

def check_ascii (varname, value):
  u"""
  checks if value contains only ASCII characters

  :param varname: variable name, useful for debug
  :param value: value to test
  """
  if type(varname) != unicode :
    raise TypeError, error(tr_(u'E001 - varname: unicode expected, got %(TYPE)s') % dict(
      TYPE=type(varname).__name__))
  if not isinstance(value, basestring) :
    raise TypeError, error(tr_(u'E001 - value: str or unicode expected, got  %(TYPE)s') % dict(
      TYPE=type(varname).__name__))

  if isinstance(value, str) :
    try :
      value.decode(u'ascii')
    except UnicodeEncodeError :
      raise ValueError, error(tr_(u'E003 - %(STRING)s: ASCII string required') % dict(
        STRING=varname))
  elif isinstance(value, unicode) :
    try :
      value.encode(u'ascii')
    except UnicodeEncodeError :
      raise ValueError, error(tr_(u'E003 - %(STRING)s: ASCII string required') % dict(
        STRING=varname))

def _check_var_naming_convention (varname, pattern, conv_name):
  u"""
  :param varname: string to test
  :type varname: str or unicode
  :param pattern: compiled regular expression, see `re` module
  :type pattern: pattern object
  :param conv_name: naming convention name (ex: python, CMake, ...)
  :type conv_name: unicode 
  """
  check_ascii(u'varname', varname)
  match_obj = pattern.match(varname)
  if match_obj :
    if match_obj.group() != varname :  # not allowed separator like "," found
      raise ValueError, \
      error(tr_(u"E004 - name '%(NAME)s' must respect %(CONVENTION)s "\
         u"variable naming conventions") % dict(
          NAME=varname, CONVENTION=conv_name))
  else :
    raise ValueError, \
      error(tr_(u"E004 - name '%(NAME)s' must respect %(CONVENTION)s "\
         u"variable naming conventions") % dict(
          NAME=varname, CONVENTION=conv_name))

def check_var_naming_convention (varname):
  _check_var_naming_convention(varname, regexpr.co_varname, u'pyLot')

def check_pyvar_naming_convention (varname):
  _check_var_naming_convention(varname, regexpr.co_pyvarname, u'python')

def check_stream (varname, value):
  u"""
  currently do nothing
  """
  return

def arrange_alias (identifier, alias):
  check_type(u'identifier', identifier, unicode, none=False)
  check_var_naming_convention(identifier)
  check_type(u'alias', alias, unicode, none=True)

  if alias is None :
    return identifier
  else :
    return unicode(alias)

def get_datatest(filename):
  u"""
  Return full path for test "filename".
  filename is supposed to be in tests/test_data dir in doctest module
  """
  return get_data(u'doctests', u'tests/test_data/%s' % filename)

def get_data(name, path):
  """
  Eases access to module packed data.
  
  This function return an absolute path using 
  :obj:`pkg_resources.resource_filename` function.

  :param name: __name__
  :param path: path relative to module "name"
  """
  path = pkg_resources.resource_filename(name, path)
  path = Path(path).abspath()
  return path

def find_file (relpath, dirlist):
  u"""
  Find "relpath" in dirlist directories.
  find_file is NOT recursive

  ex : find_file(u'python.pdf', dirlist=[u'/usr/share/doc', u'/home/guillaume/doc'])
  -> /home/guillaume/doc/python.pdf

  Real example : search where is defined utils file among two directories

  >>> from pyLot.core import get_data
  >>> p1 = get_data('_pylot', u'core')
  >>> p2 = get_data('_pylot', u'minimal')
  >>> find_file(u'utils.py', dirlist=[p1, p2])
  Path(u'.../_pylot/minimal/utils.py')

  :param relpath: file you want to search relatively to dirlist
  :type relpath: str or :class:`~_pylot.minimal.Path`

  :param dirlist: list of directory which can contain relpath
  :type dirlist: str or :class:`~_pylot.minimal.Path`
  """
  check_type(u'relpath', relpath, [unicode, Path])

  if not hasattr(dirlist, '__iter__') :
    dirlist = (dirlist,)
  filepath = None
  for directory in dirlist :
    fp = Path(directory, relpath)
    if fp.exists() :
      filepath = fp
      break
  if type(filepath) is Path :
    return filepath.abspath()
  else :
    return filepath

def walk_files (pathlist, pattern=u'*'):
  u"""
  
  like Path.walkfiles but accepts Path, unicode, list of Path, ...
  Returns a list of alphabetically ordered paths
  
  ex:
  walk_files(u'/path', u'*.py')
  [u'f1.py', u'autre.py']
  walk_files('/path/autre.py', u'*.txt')
  []
  walk_files('/path/autre.py', u'*.py')
  [u'autre.py']
  walk_files(u'/not/exists/', u'*.txt')
  []

  :param pathlist: path to walk in. Can be file or directory path.
  :type pathlist: L{Path<_pylot.minimal.path.Path>} or str
  :param pattern: a simple pattern like '*.txt', 'README*', ...
  :type pattern: unicode

  :return: matching file list
  :rtype: list
  """
  check_type(u'path', pathlist, [unicode, Path, list, tuple, set])
  check_type(u'pattern', pattern, unicode)

  all_path = set([])
  if not isinstance(pathlist, (list, tuple, set)) :
    pathlist = [pathlist]

  for path in pathlist :
    path = Path(path)
    if path.isfile() :
      all_path.add(path.abspath())
    elif path.isdir() :
      for p in path.walkfiles(pattern) :
        all_path.add(p.abspath())
  all_path = list(all_path)
  all_path.sort()
  return all_path

def normalize_line_separator (string):
  check_type(u'string', string, unicode)

  for linesep in (u'\r\n', u'\r\x85', u'\r', u'\x85', u'\u2028') :
    if linesep in string :
      string = string.replace(linesep, u'\n')
  return string

def copy_file (src, dest):
  u"""
  if dest exists, overwrite it
  if dest is a directory, copy src on it
  if destination directory doesn't exists, create it (and parents if necessary)
  """
  check_type(u'src', src, [unicode, Path])
  check_type(u'dest', dest, [unicode, Path])

  src = Path(src).abspath()
  if src :
    dest = Path(dest).abspath()
    if dest.isfile() :
      dest.remove()
    elif dest.isdir() :
      dest = Path(dest, src.name)
    elif not dest.exists() :
      if src.name == dest.name and not dest.parent.exists() :
        dest.parent.makedirs()
      elif src.name != dest.name and not dest.exists() :
        dest.makedirs()
        dest = Path(dest, src.name)

    src.copyfile(dest)
    return dest
  else :
    raise ValueError, error(tr_(u'file %s not exists') % src)


def safe_replaceable (path):
  u"""
  if return True, you can replace this path because nothing exists
  or a backup has been done. Else, function return False
  """
  check_type(u'path', path, [unicode, Path])
  safe = False
  path = Path(path)
  if path.exists() :
    safe = backup_file(path)
  else : safe = True

  return bool(safe)


def backup_file (filename, backup_dir=None):
  u"""
  Backups file `filename`

  filename is copied to filename-01, if filename-01 exists, it's copied
  to filename-02, ...

  :param filename: file you want to backup
  :type filename: str or :class:`~_pylot.minimal.Path`

  :param backup_dir: the directory where you want to backup file. Default: filename directory
  :type backup_dir: :obj:`None`, :obj:`unicode` or :class:`~_pylot.minimal.Path`

  :return: backuped file path or None if backup failed
  :rtype: :class:`~_pylot.minimal.Path` or None if copy fails
  """
  check_type(u'filename', filename, [unicode, Path])
  check_type(u'backup_dir', backup_dir, [unicode, Path], none=True)

  filename = Path(filename)
  if backup_dir is None :
    save = suggest_name(filename)
  else :
    backup_dir = Path(backup_dir)
    save = suggest_name(Path(backup_dir, filename.name))
    if not backup_dir.exists() :
      backup_dir.makedirs()

  try :
    filename.copy(save)
  except (IOError, OSError), e :
    raise e
  else :
    return save

def suggest_name (filename):
  check_type(u'filename', filename, [unicode, Path])

  filename = Path(filename)
  if not filename.exists() :
    save = filename
  else :
    i = 1
    save = Path(u'%s-%02d' % (filename, i))
    while save.exists() :
      i += 1
      save = Path(save.replace(unicode(i - 1), unicode(i)))
  return save


def readfile (filename, mode='r', encoding=u'utf-8'):
  u"""
  merge codecs.open and open in one function
  
  :param encoding: file encoding. if None, file is opened in byte mode instead of unicode mode
  """
  check_type(u'filename', filename, [unicode, Path])
  check_type(u'encoding', encoding, unicode, none=True)

  filename = Path(filename)

  if encoding :
    return codecs.open(filename.abspath(), mode, encoding)
  else :
    return open(filename.abspath(), mode)



def writefile (filename, security=u'safe', mode=u'w', encoding=u'utf-8'):
  u"""
  :param encoding: file encoding. if None, file is opened in byte mode instead of unicode mode
  """
  check_type(u'filename', filename, [unicode, Path])
  check_type(u'security', security, unicode)
  check_value(u'security', security, [u'force', u'default', u'safe'])
  check_ascii(u'mode', mode)
  check_type(u'encoding', encoding, unicode)

  crashfile = None
  write = False
  filename = Path(filename).abspath()
  if filename.exists() :
    if security == u'force' :
      write = True
#      print >> sys.stderr, 'file %s replaced' %filename.abspath()
    elif security == u'default' :
      uprint(u'file %s exists, replace' % filename.abspath(), file=sys.stderr)
      if raw_input(u' ').lower() in (u'y', u'yes') :
        write = True
    else :
      uprint(u'file %s exists, nothing done' % filename.abspath(), file=sys.stderr)
  else :
    write = True

  if write :
    if not filename.parent.exists() :
      try :
        filename.parent.makedirs()
      except OSError :
        uprint(u'cannot create parent dir', file=sys.stderr)
        raise OSError
        openfile = crashfile
      else :
        pass
    try :
      if encoding is None :
        openfile = open(filename.abspath(), mode)
      else :
        openfile = codecs.open(filename.abspath(), mode, encoding)
    except IOError:
      uprint(u'cannot write %s, redirect to stdout' % filename.abspath(), file=sys.stderr)
      raise IOError
      openfile = crashfile
    else :
      pass
  else :
    raise OSError
    openfile = crashfile
  return openfile

class Stopwatch (object):
  u"""
  Simple stopwatch
  
  >>> t = Stopwatch()
  >>> float(t)
  -1.0
  >>> t.start()
  >>> float(t) #doctest:+SKIP
  0.0002
  >>> float(t) #doctest:+SKIP
  0.0004
  >>> t.stop()
  >>> t_end = float(t)
  >>> import time ; time.sleep(1)
  >>> float(t) == t_end
  True
  """
  def __init__ (self):
    object.__init__(self)
    self._start_time = None
    self._stop_time = None

  def _gettime (self):
    u"""
    if not running, return -1
    if running, return time since start time
    if stopped, return execution time
    """
    if self._start_time is None :
      return -1
    elif self._stop_time is None :
      return time.time() - self._start_time
    else :
      return self._stop_time - self._start_time

  time = property(fget=_gettime)

  def start (self):
    u"""
    Start stopwatch. If stopwatch is running, stop it, reset time and start
    """
    self._stop_time = None
    self._start_time = time.time()

  def stop (self):
    u"""
    Stop stopwatch. If not running, reset stopwatch
    """
    if self._start_time is None :
      self._stop_time = None
    else :
      self._stop_time = time.time()

  def reset (self):
    self._start_time = None
    self._stop_time = None

  def __float__ (self):
    return float(self.time)

  def __int__ (self):
    return int(self.time)

CREATED_PATHS = set([])
u""" a set of path created by pylot code. This set is used with "safe remove"
commands. Principle is to delete only paths created by ourself.
So, if a path is in created_paths, it can be deleted
"""

# created_paths variable filling
try :
  cp = readfile(Path(u'~', u'.pylot', u'.cp').expanduser())
except (IOError, OSError) :
  pass
else :
  try : CREATED_PATHS = pickle.load(cp)
  except (EOFError, KeyError) :
    pass

if __name__ == u"__main__":
  import doctest
  doctest.testmod()

